
#include <string.h>
#include "rthw.h"
#include "rtthread.h"
#include "board.h"
#include "ev_trans.h"
#include "ev_tick_hook.h"


static struct rt_semaphore trans_tx_sem;
static struct rt_mutex trans_tx_mutex;

// ipc rx_packet size is sizeof(ev_packet_t)
#define TRANS_RX_PACKET_COUNT  8
ALIGN(8) static uint8_t trans_rx_mq_buf[sizeof(ev_packet_t) * TRANS_RX_PACKET_COUNT];
static struct rt_messagequeue trans_rx_mq;

// one uart_rx frame max size is EV_PACKET_MAX_LEN
static volatile uint8_t trans_uart_rx_timer;
static ev_parser_t trans_parser;

// _check_trans_rx_timeout is registered in tickhook where irq is already disabled
// so it need not to disable irq.
static void _check_trans_rx_timeout(void)
{
    // rt_base_t level = rt_hw_interrupt_disable();
    if (trans_uart_rx_timer > 1) {
        trans_uart_rx_timer --;
    } else if (trans_uart_rx_timer == 1) {
        ev_parser_reset(&trans_parser);
        trans_uart_rx_timer = 0;
    } else {
        trans_uart_rx_timer = 0;
    }
    // rt_hw_interrupt_enable(level);
}

static void trans_parser_cb(const ev_parser_t *parser, const ev_packet_t *pkt)
{
    (void)parser;
    rt_mq_send(&trans_rx_mq, pkt, sizeof(ev_packet_t));
}

int ev_trans_init(uint32_t baudrate)
{
    rt_err_t err;
    err = rt_sem_init(&trans_tx_sem, "transtx", 0, RT_IPC_FLAG_FIFO);
    if (err != RT_EOK) {
        rt_kprintf("trans_tx_sem init failed\n");
        while (1);
    }
    err = rt_mutex_init(&trans_tx_mutex, "transtx", RT_IPC_FLAG_FIFO);
    if (err != RT_EOK) {
        rt_kprintf("trans_tx_mutex init failed\n");
        while (1);
    }
    err = rt_mq_init(&trans_rx_mq, "transrx", trans_rx_mq_buf, sizeof(ev_packet_t),
                     sizeof(trans_rx_mq_buf), RT_IPC_FLAG_FIFO);
    if (err != RT_EOK) {
        rt_kprintf("trans_mq init failed\n");
        while (1);
    }
    trans_uart_rx_timer = 0;
    ev_tick_hook_register(&_check_trans_rx_timeout);
    ev_parser_init(&trans_parser, NULL, &trans_parser_cb);
    driv_trans_uart_init(baudrate);

    return 0;
}

int ev_trans_tx(const ev_packet_t *packet)
{
    static uint8_t tx_buf[EV_PACKET_MAX_LEN] __attribute__((aligned(8)));
    uint32_t tx_len;
    rt_mutex_take(&trans_tx_mutex, RT_WAITING_FOREVER);
    int ret = ev_packet_make(packet, tx_buf, &tx_len);
    if (ret != 0) {
        rt_mutex_release(&trans_tx_mutex);
        return -1;
    }
    if (tx_len > 0) {
        driv_trans_uart_write(tx_buf, tx_len);
        rt_sem_take(&trans_tx_sem, RT_WAITING_FOREVER);
    }
    rt_mutex_release(&trans_tx_mutex);
    return 0;
}

int ev_trans_rx(ev_packet_t *packet, int32_t timeout_ms)
{
    if (!packet) {
        return -1;
    }
    rt_err_t ret = rt_mq_recv(&trans_rx_mq, (void *)packet, sizeof(ev_packet_t), timeout_ms);
    return ret;
}

// driver trans uart callback implement
void driv_trans_uart_tx_cb(void)
{
    rt_interrupt_enter();
    rt_sem_release(&trans_tx_sem);
    rt_interrupt_leave();
}

void driv_trans_uart_rx_cb(uint8_t c)
{
    rt_interrupt_enter();
    rt_base_t level = rt_hw_interrupt_disable();
    ev_parser_feed_one(&trans_parser, c);
    trans_uart_rx_timer = 5; // 5ms timeout
    rt_hw_interrupt_enable(level);
    rt_interrupt_leave();
}

#include "ev_cmd_type.h"
void ev_trans_log(const char *format, ...)
{
    static char log_tx_buf[EV_PACKET_MAX_LEN];
    va_list args;
    uint32_t length;

    rt_mutex_take(&trans_tx_mutex, RT_WAITING_FOREVER);
    va_start(args, format);
    rt_vsnprintf(log_tx_buf,
                 EV_PACKET_MAX_DATA_LEN - 1,
                 format,
                 args
    );
    length = rt_strlen(log_tx_buf);
    va_end(args);
    if (length > EV_PACKET_MAX_DATA_LEN) {
        length = EV_PACKET_MAX_DATA_LEN;
    }

    ev_packet_t pkt;
    pkt.seq = 0;
    pkt.user = 0;
    pkt.cmd = EV_NOTIFY_DEVICE_LOG;
    pkt.len = length;
    memcpy(pkt.data, log_tx_buf, length);
    ev_packet_make(&pkt, (uint8_t *)log_tx_buf, &length);

    if (length > 0) {
        driv_trans_uart_write((uint8_t *)log_tx_buf, length);
        rt_sem_take(&trans_tx_sem, RT_WAITING_FOREVER);
    }
    rt_mutex_release(&trans_tx_mutex);
}
